﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.IO.IsolatedStorage;
using System.Data.Linq;

/*=====================================================================
  This file is part of a Microsoft SQL Server Shared Source Application.
  Copyright (C) Microsoft Corporation.  All rights reserved.
 
THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
======================================================= */

/// This file contains sample application specific code for the data access layer of this solution.  It provides the API
/// the user interface code in the ProductEditor project uses to initiate data operations.  This code uses the DataContext 
/// object and the Mirroring custom .NET Data Provider 
/// defined in a separates file in this project to implement this API.

namespace Microsoft.SqlCommunity.FaultRetry
{
    /// <summary>
    /// Represents the layer of software used by the user interface to act on data stored in the 
    /// AdventureWorksLT 2008 sample database.
    /// </summary>
    public class DataAccess
    {
        const string DatabaseName = "AdventureWorksLT2008";  // Which database to act on
        const string NetworkLibraryName = "dbmssocn";  // Use TCP/IP

        private static DataAccess _current = null;  // The singleton instance used for data operations

        /// <summary>
        /// The singleton instance used for data operations
        /// </summary>
        public static DataAccess Current
        {
            get
            {
                if (_current == null)
                    _current = new DataAccess();
                return _current;
            }
        }

        private ServerSet _currentServers = null;   // The principal and mirror servers where the database resides

        private FaultRetryConnection _connection = null; // The mirroring connection used to invoke commands 

        private DataContext _context = null;  // The object which manages all the associated objects with the ProductDataSet

        /// <summary>
        /// The object which manages all the associated objects with the ProductDataSet
        /// </summary>
        public DataContext Context
        {
            get 
            {
                if (_connection == null || _context == null)
                {
                    ResetConnection();
                }
                return _context; 
            }
        }

        /// <summary>
        /// Initialize an object (typically a UI object, specificially in this application the configuration dialog object)
        /// with the list of all principal and mirror servers.  Read this list (if possible) from isolated storage.
        /// </summary>
        /// <param name="serverListSource">The object which will retain the list of principal and mirror servers.</param>
        public void InitializeServerLists(IServerListsSource serverListSource)
        {
            serverListSource.AllServers = new ServerSets();

            try
            {
                IsolatedStorageFile serverNameFile = IsolatedStorageFile.GetUserStoreForAssembly();
                using (IsolatedStorageFileStream serverNameFileStream =
                    new IsolatedStorageFileStream("ServerNames", FileMode.OpenOrCreate, serverNameFile))
                using (StreamReader serverFileNameReader = new StreamReader(serverNameFileStream))
                {
                    string serverLine = serverFileNameReader.ReadLine();
                    //If the file is empty just leave the collection of server names empty.
                    if (serverLine == null) return;
                    string[] serverNames = serverLine.Split(',');

                    if (serverNames.Length > 1)
                    {
                        //Otherwise, try and get mirror server names as well as primaries.
                        for (int i = 0; i + 1 < serverNames.Length; i += 2)
                        {
                            serverListSource.AllServers.Add(new ServerSet(serverNames[i], serverNames[i + 1]));
                        }

                        serverListSource.CurrentServers = serverListSource.AllServers[0];
                        this._currentServers = serverListSource.CurrentServers;
                    }

                }
            }
            //If we can't read the list of server names, don't bother to error out.  We can get by without them.
            catch (IOException)
            {
            }
            //Likeswise if for some reason we don't have permission to access isolated storage
            catch (System.Security.SecurityException)
            {
            }
        }

        /// <summary>
        /// Delete any saved history of mirrored servers used from isolated storage.
        /// </summary>
        public void ClearServerLists()
        {
            IsolatedStorageFile serverNameFile = IsolatedStorageFile.GetUserStoreForAssembly();
            serverNameFile.DeleteFile("ServerNames");

        }

        /// <summary>
        /// This handler should be added to the event raised by a UI object (in this case the ConfigureDialog object)
        /// when the order or set of servers is changed by the user.  This handler will persist the current list of 
        /// principal and mirror servers in isolated storage.  Any existing data context will be closed, and 
        /// a new one will be opened with the current principal and mirror servers.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="eventArgs"></param>
        public void ServerListsChangedHandler(object sender, EventArgs eventArgs)
        {
            IServerListsSource serverListSource = sender as IServerListsSource;
            if (serverListSource != null)
            {
                IsolatedStorageFile serverNameFile = IsolatedStorageFile.GetUserStoreForAssembly();
                using (IsolatedStorageFileStream serverNameFileStream =
                    new IsolatedStorageFileStream("ServerNames", FileMode.Create, serverNameFile))
                //Output p1,m1,p2,m2,...,pn,mn format for all principals and mirrors into isolated storage
                using (StreamWriter serverFileNameWriter = new StreamWriter(serverNameFileStream))
                {
                    bool isFirst = true;
                    foreach (ServerSet servers in serverListSource.AllServers)
                    {
                        if (isFirst)
                            isFirst = false;
                        else
                            serverFileNameWriter.Write(",");
                        serverFileNameWriter.Write(servers.ToString());
                    }
                    serverFileNameWriter.WriteLine();
                }

                _currentServers = serverListSource.CurrentServers;
                ResetConnection();
            }
        }

        /// <summary>
        /// Close out the current data context (if any) and reopen with the currently specified principal and mirror servers.
        /// </summary>
        public void ResetConnection()
        {
            if (_context != null)
            {
                _context.Close();
            }

            if (_connection != null)
            {
                _connection.Close();
                _connection = null;
            }



            //We use this to avoid injection attacks via the connection string.
            SqlConnectionStringBuilder connBuilder = new SqlConnectionStringBuilder();
            connBuilder.DataSource = _currentServers.Principal;
            connBuilder.FailoverPartner = _currentServers.Mirror;
            connBuilder.InitialCatalog = DatabaseName;
            connBuilder.NetworkLibrary = NetworkLibraryName;
            connBuilder.IntegratedSecurity = true;

            _connection = new FaultRetryConnection(connBuilder.ToString());
            _context = new DataContext(_connection);

        }

        /// <summary>
        /// Saves any changes which have been made to data context.
        /// </summary>
        public void SaveChanges()
        {
            //TODO: Deal with conflicting changes from other users.
            this.Context.SubmitChanges();
        }

        /// <summary>
        /// Use LINQ to dataset to return a set of all the top level catagories.
        /// </summary>
        /// <returns>The set of all top level categories</returns>
        public IEnumerable<ProductCategory> GetCategories()
        {
            return from category in this.Context.ProductCategories
                        where category.IsParentProductCategoryIDNull()
                        select category;
        }

        /// <summary>
        /// Determines if any data in the data context has changed.
        /// </summary>
        /// <returns>True if there are any changes to the data</returns>
        public bool IsDataChanged()
        {
            if (_context == null) return false;
            return _context.IsDataChanged();
        }

        /// <summary>
        /// Determines the set of products that belong to the specified category.
        /// </summary>
        /// <param name="category">Which category to filter on</param>
        /// <returns>The set of products in the category</returns>
        public IEnumerable<Product> GetProducts(ProductCategory category)
        {
            return from product in this.Context.Products
                   where product.ProductCategoryID == category.ProductCategoryID
                   select product;
        }

    }
    
}
